package cn.ibizlab.util.helper;

import cn.hutool.core.util.ReflectUtil;
import cn.ibizlab.util.annotation.Audit;
import cn.ibizlab.util.annotation.DEField;
import cn.ibizlab.util.enums.DEFieldDefaultValueType;
import cn.ibizlab.util.enums.DEPredefinedFieldType;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.util.TypeUtils;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.util.*;

public class BeanCache {

    private BeanCache() {
        throw new IllegalStateException("Utility class");
    }

    private static Map<String,BeanSchema> cache=new HashMap<>();
    private static Object objLock1=new Object();
    private static Set<String> ignoFields=new HashSet<>(Arrays.asList("handler","_filter_signature","serialVersionUID","_methods_"));
    private static BeanSchema EMPTY = new BeanSchema();

    private static FieldItem EMPTY_FIELD = new FieldItem().setCodeName("").setFieldName("").setColumnName("").setJsonName("");

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    public static class BeanSchema{
        private String codeName;
        private String name;
        private String pluralize;
        private String logicName;
        private FieldItem keyField;

        private boolean hasAudit=false;

        private FieldItem orgField;  //组织属性
        private FieldItem orgNameField;  //组织名称属性
        private FieldItem orgDeptField; //部门属性
        private FieldItem orgDeptNameField; //部门名称属性
        private FieldItem orgTeamField; //组属性
        private FieldItem  createManField; //创建人属性
        private FieldItem  createManNameField; //创建人名称属性
        private FieldItem  updateManField; //更新人属性
        private FieldItem  updateManNameField; //更新人名称属性
        private FieldItem  createTimeField; //创建时间
        private FieldItem  updateTimeField; //更新时间
        private FieldItem  logicValidField; //逻辑删除属性
        private Map<String, FieldItem> fieldMap=new LinkedHashMap<>();

        private List<FieldItem> fields=new ArrayList<>();
        private List<FieldItem> deFields=new ArrayList<>();
        private List<FieldItem> audits=new ArrayList<>();

        public boolean containsKey(String tag) {
            if(fieldMap.containsKey(tag)) {
                return true;
            }
            else if(fieldMap.containsKey(tag.toLowerCase())) {
                return true;
            }
            else if(fieldMap.containsKey(tag.toLowerCase().replace("_",""))) {
                return true;
            }
            else {
                return false;
            }
        }

        public FieldItem get(String tag) {
            if(fieldMap.containsKey(tag)) {
                return fieldMap.get(tag);
            }
            else if(fieldMap.containsKey(tag.toLowerCase())) {
                return fieldMap.get(tag.toLowerCase());
            }
            else if(fieldMap.containsKey(tag.toLowerCase().replace("_",""))) {
                return fieldMap.get(tag.toLowerCase().replace("_",""));
            }
            else {
                return null;
            }
        }

        public FieldItem getField(String tag) {
            FieldItem item = get(tag);
            if (item == null)
                return EMPTY_FIELD;
            return item;
        }

        public String getFieldName(String tag,String defaultName) {
            FieldItem item = get(tag);
            if (item == null)
                return defaultName;
            return item.getFieldName();
        }

        public String translateFieldName(String tag) {
            return getFieldName(tag,tag);
        }

        public Audit getAudit(String tag) {
            FieldItem item=fieldMap.get(tag);
            if(item!=null)
                return item.getAudit();
            return null;
        }

        public DEField getDEField(String tag) {
            FieldItem item=fieldMap.get(tag);
            if(item!=null)
                return item.getDeField();
            return null;
        }


        public  static <T> BeanSchema from(Class<T> clazz){
            String className=clazz.getSimpleName();
            if(className.indexOf("_$")>=0) {
                className=className.substring(0, className.lastIndexOf("_$"));
            }
            if(cache.containsKey(className)) {
                return cache.get(className);
            }
            synchronized (objLock1) {
                if (cache.containsKey(className)) {
                    return cache.get(className);
                }
                BeanSchema schema=new BeanSchema().setCodeName(className).setName(className).setPluralize(StringAdvUtils.pluralize(className));
                Map<String, FieldItem> result=schema.getFieldMap();
                List<FieldItem> list = schema.getFields();

                ApiModel apiModel=clazz.getAnnotation(ApiModel.class);
                if(!ObjectUtils.isEmpty(apiModel)) {
                    if(!ObjectUtils.isEmpty(apiModel.description())) {
                        schema.setLogicName(apiModel.description());
                        if(!ObjectUtils.isEmpty(apiModel.value()))
                            schema.setName(apiModel.value());
                    }
                    else if(!ObjectUtils.isEmpty(apiModel.value()))
                        schema.setLogicName(apiModel.value());
                }

                if(clazz.getAnnotation(Audit.class)!=null)
                    schema.setHasAudit(true);

                Field[] fields= ReflectUtil.getFields(clazz);
                for(Field field:fields){
                    if(field.getAnnotation(Transient.class)!=null)
                        continue;
                    if(ignoFields.contains(field.getName()))
                        continue;
                    FieldItem item=new FieldItem().setCodeName(field.getName()).setFieldName(field.getName()).setColumnName(field.getName().toLowerCase()).setJsonName(field.getName().toLowerCase()).setLogicName(field.getName()).setField(field);

                    JSONField jsField=field.getAnnotation(JSONField.class);
                    if(!ObjectUtils.isEmpty(jsField)) {
                        if(!ObjectUtils.isEmpty(jsField.name()))
                            item.setJsonName(jsField.name());
                        if(!ObjectUtils.isEmpty(jsField.format()))
                            item.setFormat(jsField.format());
                    }
                    else {
                        JsonProperty jsonProperty=field.getAnnotation(JsonProperty.class);
                        if((!ObjectUtils.isEmpty(jsonProperty))&&(!ObjectUtils.isEmpty(jsonProperty.value())))
                            item.setJsonName(jsonProperty.value());
                    }

                    ApiModelProperty apiModelProperty=field.getAnnotation(ApiModelProperty.class);
                    if(!ObjectUtils.isEmpty(apiModelProperty)) {
                        if(!ObjectUtils.isEmpty(apiModelProperty.notes())) {
                            item.setLogicName(apiModelProperty.notes());
                            if (!ObjectUtils.isEmpty(apiModelProperty.value()))
                                item.setFieldName(apiModelProperty.value()).setColumnName(apiModelProperty.value());
                        }
                        else if (!ObjectUtils.isEmpty(apiModelProperty.value()))
                            item.setLogicName(apiModelProperty.value());
                    }

                    org.springframework.data.mongodb.core.mapping.Field mogoField=field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
                    if(!ObjectUtils.isEmpty(mogoField) && !ObjectUtils.isEmpty(mogoField.name()))
                        item.setFieldName(mogoField.name()).setColumnName(mogoField.name());


                    DEField deField=field.getAnnotation(DEField.class);
                    if(!ObjectUtils.isEmpty(deField)) {
                        if(!ObjectUtils.isEmpty(deField.name())) {
                            item.setFieldName(deField.name()).setColumnName(deField.name());
                        }
                        item.setDeField(deField);
                        schema.getDeFields().add(item);
                        if(deField.isKeyField())
                            schema.setKeyField(item);

                        switch (deField.preType()) {
                            case CREATEMAN:
                                schema.setCreateManField(item);
                                break;
                            case CREATEMANNAME:
                                schema.setCreateManNameField(item);
                                break;
                            case UPDATEMAN:
                                schema.setUpdateManField(item);
                                break;
                            case UPDATEMANNAME:
                                schema.setUpdateManNameField(item);
                                break;
                            case CREATEDATE:
                                schema.setCreateTimeField(item);
                                break;
                            case UPDATEDATE:
                                schema.setUpdateTimeField(item);
                                break;
                            case ORGID:
                                schema.setOrgField(item);
                                break;
                            case ORGNAME:
                                schema.setOrgNameField(item);
                                break;
                            case ORGSECTORID:
                                schema.setOrgDeptField(item);
                                break;
                            case ORGSECTORNAME:
                                schema.setOrgDeptNameField(item);
                                break;
                            case LOGICVALID:
                                schema.setLogicValidField(item);
                                break;
                            default:
                                break;
                        }
                        if(deField.defaultValueType().equals(DEFieldDefaultValueType.SESSION)&&deField.defaultValue().toLowerCase().indexOf("teamid")>=0)
                            schema.setOrgTeamField(item);
                    }

                    Audit audit=field.getAnnotation(Audit.class);
                    if(!ObjectUtils.isEmpty(audit)) {
                        item.setAudit(audit);
                        schema.getAudits().add(item);
                        schema.setHasAudit(true);
                    }


                    TableField tableField=field.getAnnotation(TableField.class);
                    if(!ObjectUtils.isEmpty(tableField)) {
                        if(tableField.exist()&&(ObjectUtils.isEmpty(tableField.value()))) {
                            item.setFieldName(tableField.value()).setColumnName(tableField.value());
                        }
                        else if(!tableField.exist())
                            item.setColumnName("");
                    }
                    TableId tableId=field.getAnnotation(TableId.class);
                    if(!ObjectUtils.isEmpty(tableId)) {
                        if(!ObjectUtils.isEmpty(tableId.value())) {
                            item.setFieldName(tableId.value()).setColumnName(tableId.value());
                        }
                        schema.setKeyField(item);
                    }

                    if(schema.getKeyField()==null && !ObjectUtils.isEmpty(field.getAnnotation(Id.class)))
                        schema.setKeyField(item);
                    list.add(item);
                    result.put(item.getCodeName(),item);
                    result.put(item.getCodeName().toLowerCase(),item);
                    result.put(item.getFieldName(),item);
                    result.put(item.getFieldName().toLowerCase(),item);
                    result.put(item.getJsonName(),item);
                    result.put(item.getJsonName().toLowerCase(),item);
                    if(!ObjectUtils.isEmpty(item.getColumnName())) {
                        result.put(item.getColumnName(), item);
                        result.put(item.getColumnName().toLowerCase(), item);
                    }
                }

                cache.put(schema.getCodeName(),schema);
                cache.put(schema.getName(),schema);
                cache.put(schema.getCodeName().toLowerCase(),schema);
                cache.put(schema.getName().toLowerCase(),schema);
                cache.put(schema.getCodeName().toLowerCase(),schema);
                cache.put(schema.getPluralize(),schema);
                return schema;
            }
        }
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    public static class FieldItem{
        private String codeName;
        private String fieldName;
        private String columnName;
        private String jsonName;
        private String logicName;
        private String format;
        private Field field;
        private Audit audit;
        private DEField deField;
        public String getCodeName()
        {
            if(codeName!=null&&codeName.length()>1&&Character.isUpperCase(codeName.charAt(1)))
                codeName=codeName.substring(0,1).toUpperCase()+codeName.substring(1);
            return codeName;
        }
    }


    public static <T> BeanSchema register(Class<T> clazz) {
        return BeanSchema.from(clazz);
    }

    public static <T> BeanSchema get(Class<T> clazz) {
        return BeanSchema.from(clazz);
    }

    public static BeanSchema get(String tag) {
        if(cache.containsKey(tag)) {
            return cache.get(tag);
        }
        synchronized (objLock1) {
            if (cache.containsKey(tag)) {
                return cache.get(tag);
            }
        }
        return EMPTY;
    }


    public static <T> boolean hasAudit(Class<T> clazz) {
        return BeanCache.get(clazz).isHasAudit();
    }

    public static <T> List<FieldItem> getFields(Class<T> clazz) {
        return get(clazz).getFields();
    }

    public static <T> FieldItem getField(Class<T> clazz, String fieldname) {
        return BeanSchema.from(clazz).get(fieldname);
    }

    /**
     * 从缓存中查询实体对象主键
     * @param
     * @return
     */
    public static <T> String getKeyField(Class<T> clazz) {
        BeanSchema schema=BeanSchema.from(clazz);
        if(schema.getKeyField()!=null) {
            return schema.getKeyField().getCodeName();
        }
        return "";
    }



    public static <T> String getFieldRealName(Class<T> clazz,String fieldname) {
        FieldItem field=getField(clazz,fieldname);
        if(field!=null) {
            return field.getCodeName();
        }
        return "";
    }

    public static <T> String getFieldName(Class<T> clazz,String fieldname) {
        FieldItem field=getField(clazz,fieldname);
        if(field!=null) {
            return field.getFieldName();
        }
        return "";
    }

    public static <T> String getFieldColumnName(Class<T> clazz,String fieldname) {
        FieldItem field=getField(clazz,fieldname);
        if(field!=null) {
            return field.getColumnName();
        }
        return "";
    }


    public static  <T> Object fieldValueOf(Class<T> clazz,String fieldname,Object fieldValue) {
        if(fieldValue==null)
            return null;
        Object resultValue=fieldValue;
        FieldItem item=getField(clazz,fieldname);
        if(item!=null) {
            Class<?> type=item.getField().getType();

            resultValue = TypeUtils.cast(fieldValue,type,null);
        }
        return resultValue;
    }
}
